# -*- coding: utf-8 -*-
"""
数据服务模块 - AKShare数据获取服务

本文件实现了基于AKShare库的金融数据获取服务，主要功能包括：

1. 基金数据获取服务
   - fetch_fund_list(): 获取基金列表数据（东方财富数据源）
   - fetch_fund_basic_info_xq(): 获取基金基本信息（雪球数据源）
   - fetch_fund_open_fund_info_em(): 获取开放式基金信息（东方财富数据源）
   - fetch_fund_net_value(): 获取基金净值数据
   - fetch_fund_detail(): 获取基金详细信息

2. 数据同步服务
   - sync_fund_data(): 同步基金数据到数据库
   - 支持批量同步和增量更新
   - 集成事务管理，确保数据一致性
   - 完整的错误处理和日志记录

3. 数据处理工具
   - _map_fund_type(): 基金类型映射转换
   - _parse_date(): 日期格式解析和转换
   - _parse_decimal(): 数值格式解析和转换
   - 数据清洗和标准化处理

4. 数据源集成
   - 东方财富（fund_name_em, fund_open_fund_info_em）
   - 雪球（fund_individual_basic_info_xq）
   - 支持多数据源的统一接口
   - 自动处理数据源差异和格式转换

5. 错误处理和监控
   - 完整的异常捕获和处理机制
   - 详细的日志记录和错误追踪
   - 数据获取失败的降级处理
   - 支持数据获取状态监控

该服务作为系统的核心数据获取组件，为上层业务提供可靠的
金融数据支持，支持实时数据获取和定时数据同步。

Author: zhaozeliang
Date: 2025-09-06
"""

import akshare as ak  # type: ignore
import pandas as pd  # type: ignore
from datetime import datetime, timedelta
from decimal import Decimal
from django.utils import timezone  # type: ignore
from django.db import transaction  # type: ignore
from .models import FundInfo, FundNavDetail, DataSyncTask, DataSyncLog
from products.models import Product, ProductCategory
import logging

logger = logging.getLogger(__name__)

class AKShareService:
    """AKShare数据获取服务"""
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
    
    def fetch_fund_list(self, fund_type='all'):
        """
        获取基金列表 - 使用东方财富基金数据
        fund_type: 基金类型 ('all', 'stock', 'bond', 'hybrid', 'money')
        """
        try:
            # 获取公募基金基本信息
            fund_info_df = ak.fund_name_em()
            
            if fund_info_df.empty:
                self.logger.warning("未获取到基金数据")
                return []
            
            # 数据清洗和转换
            funds = []
            for _, row in fund_info_df.iterrows():
                try:
                    fund_data = {
                        'fund_code': str(row.get('基金代码', '')),
                        'fund_name': str(row.get('基金简称', '')),
                        'fund_type': self._map_fund_type(str(row.get('基金类型', ''))),
                        'pinyin_abbr': str(row.get('拼音缩写', '')),
                        'pinyin_full': str(row.get('拼音全称', '')),
                    }
                    
                    if fund_data['fund_code'] and fund_data['fund_name']:
                        funds.append(fund_data)
                        
                except Exception as e:
                    self.logger.error(f"处理基金数据失败: {e}, 数据: {row}")
                    continue
                    
            self.logger.info(f"成功获取 {len(funds)} 只基金数据")
            return funds
            
        except Exception as e:
            self.logger.error(f"获取基金列表失败: {e}")
            return []
    
    def fetch_fund_basic_info_xq(self, fund_code):
        """
        获取基金基本信息 - 雪球数据源
        """
        try:
            # 使用雪球基金基本信息接口
            fund_basic_df = ak.fund_individual_basic_info_xq(symbol=fund_code)
            
            if fund_basic_df.empty:
                self.logger.warning(f"未获取到基金 {fund_code} 的基本信息")
                return None
            
            # 转换为字典格式
            fund_info = {}
            for _, row in fund_basic_df.iterrows():
                item = str(row.get('item', ''))
                value = str(row.get('value', ''))
                fund_info[item] = value
            
            # 标准化数据格式
            standardized_info = {
                'fund_code': fund_info.get('基金代码', fund_code),
                'fund_name': fund_info.get('基金名称', ''),
                'fund_full_name': fund_info.get('基金全称', ''),
                'establishment_date': self._parse_date(fund_info.get('成立时间')),
                'fund_size': fund_info.get('最新规模', ''),
                'fund_company': fund_info.get('基金公司', ''),
                'fund_manager': fund_info.get('基金经理', ''),
                'custodian_bank': fund_info.get('托管银行', ''),
                'fund_type': fund_info.get('基金类型', ''),
                'rating_agency': fund_info.get('评级机构', ''),
                'fund_rating': fund_info.get('基金评级', ''),
                'investment_strategy': fund_info.get('投资策略', ''),
                'investment_objective': fund_info.get('投资目标', ''),
                'benchmark': fund_info.get('业绩比较基准', ''),
            }
            
            return standardized_info
             
        except Exception as e:
            self.logger.error(f"获取基金 {fund_code} 基本信息失败: {e}")
            return None
    
    def fetch_fund_open_fund_info_em(self, symbol="全部", indicator="全部"):
        """
        获取开放式基金信息 - 东方财富数据源
        symbol: 基金类型筛选
        indicator: 基金指标筛选
        """
        try:
            # 使用东方财富开放式基金信息接口
            fund_open_df = ak.fund_open_fund_info_em(symbol=symbol, indicator=indicator)
            
            if fund_open_df.empty:
                self.logger.warning(f"未获取到开放式基金数据")
                return []
            
            # 数据清洗和转换
            funds = []
            for _, row in fund_open_df.iterrows():
                try:
                    fund_data = {
                        'fund_code': str(row.get('基金代码', '')),
                        'fund_name': str(row.get('基金简称', '')),
                        'unit_nav': self._parse_decimal(row.get('单位净值')),
                        'nav_date': self._parse_date(row.get('净值日期')),
                        'daily_return': self._parse_decimal(row.get('日增长率')),
                        'weekly_return': self._parse_decimal(row.get('近1周')),
                        'monthly_return': self._parse_decimal(row.get('近1月')),
                        'quarterly_return': self._parse_decimal(row.get('近3月')),
                        'half_yearly_return': self._parse_decimal(row.get('近6月')),
                        'yearly_return': self._parse_decimal(row.get('近1年')),
                        'two_yearly_return': self._parse_decimal(row.get('近2年')),
                        'three_yearly_return': self._parse_decimal(row.get('近3年')),
                        'this_year_return': self._parse_decimal(row.get('今年来')),
                        'since_inception_return': self._parse_decimal(row.get('成立来')),
                        'management_fee': self._parse_decimal(row.get('手续费')),
                        'min_purchase': str(row.get('起购金额', '')),
                    }
                    
                    if fund_data['fund_code'] and fund_data['fund_name']:
                        funds.append(fund_data)
                        
                except Exception as e:
                    self.logger.error(f"处理开放式基金数据失败: {e}, 数据: {row}")
                    continue
                    
            self.logger.info(f"成功获取 {len(funds)} 只开放式基金数据")
            return funds
            
        except Exception as e:
            self.logger.error(f"获取开放式基金数据失败: {e}")
            return []
    
    def fetch_fund_net_value(self, fund_code, start_date=None, end_date=None):
        """
        获取基金净值数据
        fund_code: 基金代码
        start_date: 开始日期
        end_date: 结束日期
        """
        try:
            if not start_date:
                start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
            if not end_date:
                end_date = datetime.now().strftime('%Y%m%d')
            
            # 尝试使用ETF基金数据API
            try:
                net_value_df = ak.fund_etf_fund_info_em(fund=fund_code, start_date=start_date, end_date=end_date)
            except:
                # 如果ETF API失败，尝试使用基金历史净值API
                net_value_df = ak.fund_em_fund_info_em(fund=fund_code, start_date=start_date, end_date=end_date)
            
            if net_value_df.empty:
                self.logger.warning(f"基金 {fund_code} 未获取到净值数据")
                return []
            
            # 数据清洗和转换
            net_values = []
            for _, row in net_value_df.iterrows():
                try:
                    net_value_data = {
                        'nav_date': self._parse_date(row.get('净值日期')),
                        'unit_nav': self._parse_decimal(row.get('单位净值')),
                        'accumulated_nav': self._parse_decimal(row.get('累计净值')),
                        'daily_return': self._parse_decimal(row.get('日增长率')),
                    }
                    
                    if net_value_data['nav_date'] and net_value_data['unit_nav']:
                        net_values.append(net_value_data)
                        
                except Exception as e:
                    self.logger.error(f"处理净值数据失败: {e}, 数据: {row}")
                    continue
            
            self.logger.info(f"成功获取基金 {fund_code} 的 {len(net_values)} 条净值数据")
            return net_values
            
        except Exception as e:
            self.logger.error(f"获取基金 {fund_code} 净值失败: {e}")
            return []
    
    def fetch_fund_detail(self, fund_code):
        """
        获取基金详细信息
        fund_code: 基金代码
        """
        try:
            # 获取基金基本信息
            fund_info_df = ak.fund_open_fund_info_em(symbol=fund_code, indicator="基金基本信息")
            
            if fund_info_df.empty:
                self.logger.warning(f"基金 {fund_code} 未获取到详细信息")
                return None
            
            # 获取基金经理信息
            manager_df = ak.fund_open_fund_info_em(symbol=fund_code, indicator="基金经理")
            manager_name = ""
            if not manager_df.empty:
                manager_name = str(manager_df.iloc[0].get('基金经理', ''))
            
            # 数据转换
            fund_detail = {
                'fund_code': fund_code,
                'fund_name': str(fund_info_df.iloc[0].get('基金简称', '')),
                'fund_type': self._map_fund_type(str(fund_info_df.iloc[0].get('基金类型', ''))),
                'fund_company': str(fund_info_df.iloc[0].get('基金公司', '')),
                'fund_manager': manager_name,
                'establishment_date': self._parse_date(fund_info_df.iloc[0].get('成立日期')),
                'fund_size': self._parse_decimal(fund_info_df.iloc[0].get('基金规模')),
                'management_fee': self._parse_decimal(fund_info_df.iloc[0].get('管理费率')),
                'custodian_fee': self._parse_decimal(fund_info_df.iloc[0].get('托管费率')),
                'subscription_fee': self._parse_decimal(fund_info_df.iloc[0].get('申购费率')),
                'redemption_fee': self._parse_decimal(fund_info_df.iloc[0].get('赎回费率')),
            }
            
            self.logger.info(f"成功获取基金 {fund_code} 详细信息")
            return fund_detail
            
        except Exception as e:
            self.logger.error(f"获取基金 {fund_code} 详细信息失败: {e}")
            return None
    
    def sync_fund_data(self, fund_codes=None, sync_net_value=True):
        """
        同步基金数据到数据库
        fund_codes: 基金代码列表，为None时同步所有基金
        sync_net_value: 是否同步净值数据
        """
        success_count = 0
        error_count = 0
        
        try:
            with transaction.atomic():
                # 获取或创建基金产品分类
                fund_category = ProductCategory.objects.filter(name='基金').first()
                if not fund_category:
                    fund_category = ProductCategory.objects.create(
                        name='基金',
                        description='基金产品'
                    )
                
                if fund_codes is None:
                    # 获取所有基金列表
                    funds = self.fetch_fund_list()
                else:
                    # 获取指定基金的基本信息
                    all_funds = self.fetch_fund_list()
                    funds = [fund for fund in all_funds if fund['fund_code'] in fund_codes]
                
                for fund_data in funds:
                    try:
                        # 获取基金详细信息
                        fund_detail = self.fetch_fund_detail(fund_data['fund_code'])
                        if not fund_detail:
                            # 如果获取详细信息失败，尝试使用雪球数据源
                            fund_detail = self.fetch_fund_basic_info_xq(fund_data['fund_code'])
                        
                        # 合并基本信息和详细信息
                        merged_data = {
                            'fund_code': fund_data['fund_code'],
                            'fund_name': fund_data['fund_name'],
                            'fund_type': fund_data['fund_type'],
                            'fund_company': '',
                            'fund_manager': '',
                            'establishment_date': None,
                            'fund_size': None,
                            'management_fee': Decimal('0'),
                            'custodian_fee': Decimal('0'),
                        }
                        
                        if fund_detail:
                            merged_data.update({
                                'fund_company': fund_detail.get('fund_company', ''),
                                'fund_manager': fund_detail.get('fund_manager', ''),
                                'establishment_date': fund_detail.get('establishment_date'),
                                'fund_size': self._parse_decimal(fund_detail.get('fund_size')),
                                'management_fee': fund_detail.get('management_fee', Decimal('0')),
                                'custodian_fee': fund_detail.get('custodian_fee', Decimal('0')),
                            })
                        
                        # 创建或更新产品
                        product, created = Product.objects.get_or_create(
                            code=merged_data['fund_code'],
                            defaults={
                                'name': merged_data['fund_name'],
                                'category': fund_category,
                                'product_type': 'stable',
                                'risk_level': 3,  # 默认中等风险
                                'manager': merged_data['fund_manager'],
                                'company': merged_data['fund_company'],
                                'establishment_date': merged_data['establishment_date'] or timezone.now().date(),
                                'management_fee': merged_data['management_fee'],
                                'custody_fee': merged_data['custodian_fee'],
                                'subscription_fee': Decimal('0'),
                                'redemption_fee': Decimal('0'),
                                'min_investment': Decimal('1000'),
                                'status': 'active',
                            }
                        )
                        
                        if not created:
                            # 更新产品信息
                            product.name = merged_data['fund_name']
                            product.manager = merged_data['fund_manager']
                            product.company = merged_data['fund_company']
                            if merged_data['establishment_date']:
                                product.establishment_date = merged_data['establishment_date']
                            product.management_fee = merged_data['management_fee']
                            product.custody_fee = merged_data['custodian_fee']
                            product.save()
                        
                        # 创建或更新基金数据
                        fund_obj, fund_created = FundInfo.objects.get_or_create(
                            fund_code=merged_data['fund_code'],
                            defaults={
                                'fund_name': merged_data['fund_name'],
                                'fund_type': merged_data['fund_type'],
                                'fund_company': merged_data['fund_company'],
                                'fund_manager': merged_data['fund_manager'],
                                'establishment_date': merged_data['establishment_date'],
                                'fund_size': merged_data['fund_size'],
                                'management_fee': merged_data['management_fee'],
                                'custodian_fee': merged_data['custodian_fee'],
                            }
                        )
                        
                        if not fund_created:
                            # 更新基金数据
                            fund_obj.fund_name = merged_data['fund_name']
                            fund_obj.fund_type = merged_data['fund_type']
                            fund_obj.fund_company = merged_data['fund_company']
                            fund_obj.fund_manager = merged_data['fund_manager']
                            if merged_data['establishment_date']:
                                fund_obj.establishment_date = merged_data['establishment_date']
                            if merged_data['fund_size']:
                                fund_obj.fund_size = merged_data['fund_size']
                            fund_obj.management_fee = merged_data['management_fee']
                            fund_obj.custodian_fee = merged_data['custodian_fee']
                            fund_obj.save()
                        
                        # 同步净值数据
                        if sync_net_value:
                            net_values = self.fetch_fund_net_value(fund_data['fund_code'])
                            for net_value_data in net_values:
                                FundNavDetail.objects.update_or_create(
                                    fund_code=fund_obj.fund_code,
                                    nav_date=net_value_data['nav_date'],
                                    defaults={
                                        'unit_nav': net_value_data['unit_nav'],
                                        'cumulative_nav': net_value_data['accumulated_nav'],
                                        'daily_growth_rate': net_value_data.get('daily_return'),
                                    }
                                )
                        
                        success_count += 1
                        self.logger.info(f"成功同步基金 {fund_data['fund_code']}")
                        
                    except Exception as e:
                        error_count += 1
                        self.logger.error(f"同步基金 {fund_data['fund_code']} 失败: {e}")
                        continue
                
                self.logger.info(f"基金数据同步完成: 成功 {success_count}, 失败 {error_count}")
                return {'success': success_count, 'error': error_count}
                
        except Exception as e:
            self.logger.error(f"基金数据同步失败: {e}")
            return {'success': 0, 'error': 1}
    
    def _map_fund_type(self, fund_type_str):
        """映射基金类型"""
        type_mapping = {
            '股票型': 'stock',
            '债券型': 'bond',
            '混合型': 'hybrid',
            '货币型': 'money',
            '指数型': 'index',
            'QDII': 'qdii',
            'FOF': 'fof',
        }
        
        for key, value in type_mapping.items():
            if key in fund_type_str:
                return value
        
        return 'other'
    
    def sync_fund_net_values(self, fund_code, net_values):
        """
        同步基金净值数据到数据库
        
        Args:
            fund_code: 基金代码
            net_values: 净值数据列表
            
        Returns:
            int: 保存的记录数量
        """
        try:
            saved_count = 0
            
            with transaction.atomic():
                for nav_data in net_values:
                    try:
                        # 支持两种数据格式：英文键名（fetch_fund_net_value返回）和中文键名（兼容旧格式）
                        nav_date = nav_data.get('nav_date') or nav_data.get('净值日期')
                        unit_nav = nav_data.get('unit_nav') or nav_data.get('单位净值')
                        accumulated_nav = nav_data.get('accumulated_nav') or nav_data.get('累计净值')
                        daily_return = nav_data.get('daily_return') or nav_data.get('日增长率')
                        
                        if not nav_date or not unit_nav:
                            self.logger.warning(f"跳过无效净值数据: {nav_data}")
                            continue
                        
                        # 检查记录是否已存在
                        existing = FundNavDetail.objects.filter(
                            fund_code=fund_code,
                            nav_date=nav_date
                        ).first()
                        
                        if not existing:
                            # 创建新记录
                            FundNavDetail.objects.create(
                                fund_code=fund_code,
                                nav_date=nav_date,
                                unit_nav=self._parse_decimal(unit_nav),
                                cumulative_nav=self._parse_decimal(accumulated_nav),
                                daily_growth_rate=self._parse_decimal(daily_return)
                            )
                            saved_count += 1
                        else:
                            # 更新现有记录
                            existing.unit_nav = self._parse_decimal(unit_nav)
                            existing.cumulative_nav = self._parse_decimal(accumulated_nav)
                            existing.daily_growth_rate = self._parse_decimal(daily_return)
                            existing.save()
                            
                    except Exception as e:
                        self.logger.error(f"保存净值数据失败 {fund_code} {nav_data}: {e}")
                        continue
            
            self.logger.info(f"基金 {fund_code} 净值数据同步完成，保存 {saved_count} 条记录")
            return saved_count
            
        except Exception as e:
            self.logger.error(f"同步基金净值数据失败: {e}")
            return 0
    
    def _parse_date(self, date_str):
        """解析日期字符串"""
        if pd.isna(date_str) or not date_str:
            return None
        
        try:
            # 如果已经是date类型，直接返回
            if hasattr(date_str, 'year') and hasattr(date_str, 'month') and hasattr(date_str, 'day'):
                if hasattr(date_str, 'date'):
                    return date_str.date()
                else:
                    return date_str
            
            if isinstance(date_str, str):
                # 尝试多种日期格式
                for fmt in ['%Y-%m-%d', '%Y/%m/%d', '%Y%m%d']:
                    try:
                        return datetime.strptime(date_str, fmt).date()
                    except ValueError:
                        continue
            
            return None
        except Exception:
            return None
    
    def _parse_decimal(self, value):
        """解析数值"""
        if pd.isna(value) or value == '' or value is None:
            return None
        
        try:
            # 移除百分号和其他非数字字符
            if isinstance(value, str):
                # 处理带单位的数值（如"101.98亿"）
                if '亿' in value:
                    value = value.replace('亿', '').replace(',', '')
                    if value == '' or value == '--' or value == 'N/A':
                        return None
                    # 转换为以万为单位（数据库存储格式）
                    return Decimal(str(float(value) * 10000))
                elif '万' in value:
                    value = value.replace('万', '').replace(',', '')
                    if value == '' or value == '--' or value == 'N/A':
                        return None
                    return Decimal(str(value))
                else:
                    value = value.replace('%', '').replace(',', '')
                    if value == '' or value == '--' or value == 'N/A':
                        return None
            
            return Decimal(str(value))
        except (ValueError, TypeError, Exception):
            return None


# 创建服务实例
akshare_service = AKShareService()